/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.architexa.org.eclipse.gef.ui.palette.customize;

import com.architexa.org.eclipse.draw2d.ColorConstants;
import com.architexa.org.eclipse.draw2d.widgets.MultiLineLabel;
import com.architexa.org.eclipse.gef.internal.Internal;
import com.architexa.org.eclipse.gef.internal.ui.palette.ToolbarDropdownContributionItem;
import com.architexa.org.eclipse.gef.palette.PaletteEntry;
import com.architexa.org.eclipse.gef.palette.PaletteRoot;
import com.architexa.org.eclipse.gef.ui.palette.PaletteCustomizer;
import com.architexa.org.eclipse.gef.ui.palette.PaletteMessages;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuCreator;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.Assert;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.PageBook;



/**
 * This class implements a default dialog that allows customization of the different
 * entries/items on a GEF palette, i.e. the model behind the palette.
 * <p>
 * The construction of the dialog is broken down into different methods in order to allow
 * clients to further customize the appearance of the dialog, if so desired.
 * </p><p>
 * This dialog can be re-used, i.e., it can be re-opened once closed.  There is no need to
 * create a new <code>PaletteCustomizerDialog</code> everytime a palette needs to be
 * customized.
 * </p>
 * @author Pratik Shah
 * @see com.architexa.org.eclipse.gef.palette.PaletteEntry
 * @see com.architexa.org.eclipse.gef.ui.palette.PaletteCustomizer
 */
public class PaletteCustomizerDialog
	extends Dialog
	implements EntryPageContainer
{

/**
 * The unique ID for the Apply Button.  It can be used to retrieve
 * that widget from the internal map (using {@link #getWidget(int)} or
 * {@link #getButton(int)}), or to identify that widget in {@link #buttonPressed(int)}.
 */
protected static final int APPLY_ID = IDialogConstants.CLIENT_ID + 1;

/**
 * Sub-classes that need to create their own unique IDs should do so by adding to this ID.
 */
protected static final int CLIENT_ID = 16;

private HashMap widgets = new HashMap();
private HashMap entriesToPages = new HashMap();
private List actions;

private String errorMessage;
private Tree tree;
private Composite titlePage, errorPage;
private PageBook propertiesPanelContainer;
// This PageBook is used to switch the title of the properties panel to either an error
// message or the currently active entry's label
private PageBook titleSwitcher;
private PaletteCustomizer customizer;
private EntryPage activePage, noSelectionPage;
private CLabel title;
private MultiLineLabel errorTitle;
private Image titleImage;
private TreeViewer treeviewer;
private ILabelProvider treeViewerLabelProvider;
private PaletteEntry activeEntry;
private PaletteEntry initialSelection;
private PaletteRoot root;
private PropertyChangeListener titleUpdater = new PropertyChangeListener() {
	public void propertyChange(PropertyChangeEvent evt) {
		if (title == null) {
			return;
		}
		
		title.setText(((PaletteEntry)evt.getSource()).getLabel());
	}
};
private ISelectionChangedListener pageFlippingPreventer = new ISelectionChangedListener() {
	public void selectionChanged(SelectionChangedEvent event) {
		treeviewer.removePostSelectionChangedListener(this);
		treeviewer.setSelection(new StructuredSelection(activeEntry));
	}
};
private boolean isSetup = true;

/**
 * Constructs a new customizer dialog.
 * @param shell the parent Shell
 * @param customizer the customizer
 * @param root the palette root
 */
public PaletteCustomizerDialog(Shell shell, PaletteCustomizer customizer, 
								PaletteRoot root) {
	super(shell);
	this.customizer = customizer;
	this.root = root;
	setShellStyle(getShellStyle() | SWT.RESIZE);
}

/**
 * This method will be invoked whenever any <code>Button</code> created using 
 * {@link #createButton(Composite, int, String, int, ImageDescriptor)} or 
 * {@link Dialog#createButton(Composite, int, String, boolean)} is selected.
 * 
 * @see Dialog#buttonPressed(int)
 */
protected void buttonPressed(int buttonId) {
	if (APPLY_ID == buttonId) {
		handleApplyPressed();
	} else {
		super.buttonPressed(buttonId);
	}
}

/**
 * This method should be invoked by EntryPages when an error that they had earlier
 * reported (using {@link #showProblem(String)}) is fixed.  This will hide the error
 * message, enable the OK and Apply buttons and re-allow changing selection in the outline
 * tree.
 * 
 * @see com.architexa.org.eclipse.gef.ui.palette.customize.EntryPageContainer#clearProblem()
 * @see #showProblem(String)
 */
public void clearProblem() {
	if (errorMessage != null) {
		titleSwitcher.showPage(titlePage);
		getButton(IDialogConstants.OK_ID).setEnabled(true);
		getButton(APPLY_ID).setEnabled(true);
		errorMessage = null;
	}
}

/**
 * <p> 
 * NOTE: This dialog can be re-opened.
 * </p>
 * 
 * @see org.eclipse.jface.window.Window#close()
 */
public boolean close() {
	// Remove listeners
	if (activeEntry != null) {
		activeEntry.removePropertyChangeListener(titleUpdater);
	}
	
	// Save or dump changes
	// This needs to be done here and not in the handle methods because the user can
	// also close the dialog with the 'X' in the top right of the window (which 
	// corresponds to a cancel).
	if (getReturnCode() == OK) {
		save();
	} else {
		revertToSaved();
	}
	
	// Close the dialog
	boolean returnVal = super.close();
	
	// Reset variables
	entriesToPages.clear();
	widgets.clear();
	actions = null;
	activePage = null;
	tree = null;
	propertiesPanelContainer = null;
	titleSwitcher = null;
	titlePage = null;
	errorPage = null;
	title = null;
	errorTitle = null;
	treeviewer = null;
	noSelectionPage = null;
	initialSelection = null;
	activeEntry = null;
	errorMessage = null;
	isSetup = true;
		
	return returnVal;
}

/**
 * @see org.eclipse.jface.window.Window#configureShell(Shell)
 */
protected void configureShell(Shell newShell) {
	newShell.setText(PaletteMessages.CUSTOMIZE_DIALOG_TITLE);
	super.configureShell(newShell);
}

/**
 * This method should not be used to create buttons for the button bar.  Use
 * {@link Dialog#createButton(Composite, int, String, boolean)} for that.   This method
 * can be used to create any other button in the dialog.  The parent 
 * <code>Composite</code> must have a GridLayout.  These buttons will be  available
 * through {@link #getButton(int)} and {@link #getWidget(int)}.  Ensure that the various
 * buttons created by this method are given unique IDs.  Pass in a <code>null</code> image
 * descriptor if  you don't want the button to have an icon.  This method will take care
 * of  disposing the images that it creates.  {@link #buttonPressed(int)} will be called
 * when any of the buttons created by this method are clicked (selected).
 * 
 * @param	parent		The composite in which the button is to be created
 * @param	id			The button's unique ID
 * @param	label		The button's text
 * @param	stylebits	The style bits for creating the button (eg., 
 * 						<code>SWT.PUSH</code> or <code>SWT.CHECK</code>)
 * @param	descriptor	The ImageDescriptor from which the image/icon for this
 * 						button should be created
 * @return				The newly created button for convenience
 */
protected Button createButton(Composite parent, int id, String label,
						int stylebits, ImageDescriptor descriptor) {
	Button button = new Button(parent, stylebits);
	button.setText(label);
	button.setFont(parent.getFont());
	GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
	button.setLayoutData(data);
	
	button.setData(new Integer(id));
	button.addSelectionListener(new SelectionAdapter() {
		public void widgetSelected(SelectionEvent event) {
			buttonPressed(((Integer) event.widget.getData()).intValue());
		}
	});
	widgets.put(new Integer(id), button);
	
	if (descriptor != null) {
		button.setImage(new Image(parent.getDisplay(), descriptor.getImageData()));
		button.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				Image img = ((Button)e.getSource()).getImage();
				if (img != null && !img.isDisposed()) {
					img.dispose();
				}
			}
		});
	}
	
	return button;
}

/**
 * Creates the OK, Cancel and Apply buttons
 * 
 * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(Composite)
 */
protected void createButtonsForButtonBar(Composite parent) {
	super.createButtonsForButtonBar(parent);
	createButton(parent, APPLY_ID, PaletteMessages.APPLY_LABEL, false);
}

/**
 * The dialog area contains the following:
 * <UL>
 * 		<LI>Outline ({@link #createOutline(Composite)})</LI>
 * 		<LI>Properties Panel ({@link #createPropertiesPanel(Composite)})</LI>
 * </UL>
 * 
 * <p>
 * It is recommended that this method not be overridden. Override one of the methods that
 * this method calls in order to customize the appearance of the dialog.
 * </p>
 * 
 * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(Composite)
 */
protected Control createDialogArea(Composite parent) {
	Composite composite = (Composite)super.createDialogArea(parent);
	GridLayout gridLayout = (GridLayout)composite.getLayout();
	gridLayout.numColumns = 2;
	gridLayout.horizontalSpacing = 10;

	// Create the tree
	Control child = createOutline(composite);
	GridData data = new GridData(GridData.VERTICAL_ALIGN_FILL);
	data.verticalSpan = 2;
	child.setLayoutData(data);
	
	// Create the panel where the properties of the selected palette entry will
	// be shown
	child = createPropertiesPanel(composite);
	child.setLayoutData(new GridData(GridData.FILL_BOTH));
	
	// Create the separator b/w the dialog area and the button bar
	Label label = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
	data = new GridData(GridData.FILL_HORIZONTAL);
	data.horizontalSpan = 2;
	label.setLayoutData(data);

	// Select an element in the outline and set focus on the outline.
	if (initialSelection == null) {
		// We have to manually select the first item in the tree, because otherwise the
		// will scroll to show the last item, and then will select the first visible item.
		List children = getPaletteRoot().getChildren();
		if (!children.isEmpty()) {
			initialSelection = (PaletteEntry) children.get(0);
		}
	}
	if (initialSelection != null) {
		treeviewer.setSelection(new StructuredSelection(initialSelection));
	} else {
		setActiveEntry(null);
	}
	isSetup = false;
	tree.setFocus();

	return composite;
}


/**
 * Creates the outline part of the dialog.
 * 
 * <p>
 * The outline creates the following:
 * <UL>
 * 		<LI>ToolBar ({@link #createOutlineToolBar(Composite)})</LI>
 * 		<LI>TreeViewer ({@link #createOutlineTreeViewer(Composite)})</LI>
 * 		<LI>Context menu ({@link #createOutlineContextMenu()})</LI>
 * </UL>
 * </p>
 * 
 * @param container	The Composite within which the outline has to be created
 * @return 			The newly created Control that has the outline
 */
protected Control createOutline(Composite container) {
	// Create the Composite that will contain the outline
	Composite composite = new Composite(container, SWT.NONE);
	composite.setFont(container.getFont());
	GridLayout layout = new GridLayout();
	layout.horizontalSpacing = 0;
	layout.verticalSpacing = 0;
	layout.marginHeight = 0;
	layout.marginWidth = 0;
	composite.setLayout(layout);

	// Create the ToolBar
	createOutlineToolBar(composite);
	
	// Create the actual outline (TreeViewer)			
	treeviewer = createOutlineTreeViewer(composite);
	tree = treeviewer.getTree();
	
	// Create the context menu for the Tree
	tree.setMenu(createOutlineContextMenu());
	
	return composite;
}

/**
 * Creates the actions that manipulate the palette model.  These actions will populate the
 * toolbar and the outline's context menu.
 * 
 * <p>
 * IMPORTANT: All the elements in the returned List MUST be
 * <code>PaletteCustomizationAction</code>s.
 * </p>
 * 
 * @return A List of {@link PaletteCustomizationAction PaletteCustomizationActions}
 */
protected List createOutlineActions() {
	List actions = new ArrayList();
	actions.add(new NewAction());
	actions.add(new DeleteAction());
	actions.add(new MoveDownAction());
	actions.add(new MoveUpAction());
	return actions;
}

/**
 * Uses a <code>MenuManager</code> to create the context menu for the outline.  The
 * <code>IActions</code> used to create the context menu are those created in 
 * {@link #createOutlineActions()}.
 * 
 * @return	The newly created Menu
 */
protected Menu createOutlineContextMenu() {
	// MenuManager for the tree's context menu
	final MenuManager outlineMenu = new MenuManager();
	
	List actions = getOutlineActions();
	// Add all the actions to the context menu
	for (Iterator iter = actions.iterator(); iter.hasNext();) {
		IAction action = (IAction) iter.next();
		if (action instanceof IMenuCreator)
			outlineMenu.add(new ActionContributionItem(action) {
				public boolean isDynamic() {
					return true;
				}
			});
		else
			outlineMenu.add(action);
		// Add separators after new and delete
		if (action instanceof NewAction || action instanceof DeleteAction) {
			outlineMenu.add(new Separator());
		}
	}

	outlineMenu.addMenuListener(new IMenuListener() {
		public void menuAboutToShow(IMenuManager manager) {
			outlineMenu.update(true);
		}
	});
	
	outlineMenu.createContextMenu(tree);
	return outlineMenu.getMenu();
}

/**
 * Uses a ToolBarManager to create the ToolBar in the outline part of the
 * dialog.  The Actions used in the ToolBarManager are those that are
 * created in {@link #createOutlineActions()}.
 * 
 * @param	parent		The Composite to which the ToolBar is to be added
 * @return				The newly created ToolBar
 */
protected Control createOutlineToolBar(Composite parent) {
	// A customized composite for the toolbar 
	final Composite composite = new Composite(parent, SWT.NONE) {
		public Rectangle getClientArea() {
			Rectangle area = super.getClientArea();
			area.x += 2;
			area.y += 2;
			area.height -= 2;
			area.width -= 4;
			return area;
		}
		public Point computeSize(int wHint, int hHint, boolean changed) {
			Point size = super.computeSize(wHint, hHint, changed);
			size.x += 4;
			size.y += 2;
			return size;
		}
	};
	composite.setFont(parent.getFont());
	composite.setLayout(new FillLayout());
	
	// A paint listener that draws an etched border around the toolbar
	composite.addPaintListener(new PaintListener() {
		public void paintControl(PaintEvent e) {
			Rectangle area = composite.getBounds();
			GC gc = e.gc;
			gc.setLineStyle(SWT.LINE_SOLID);
			gc.setForeground(ColorConstants.buttonDarker);
			gc.drawLine(area.x, area.y, area.x + area.width - 2, area.y);
			gc.drawLine(area.x, area.y, area.x, area.y + area.height - 1);
			gc.drawLine(area.x + area.width - 2, area.y, 
			            area.x + area.width - 2, area.y + area.height - 1);
			gc.setForeground(ColorConstants.buttonLightest);
			gc.drawLine(area.x + 1, area.y + 1, area.x + area.width - 3, area.y + 1);
			gc.drawLine(area.x + area.width - 1, area.y + 1, 
			            area.x + area.width - 1, area.y + area.height - 1);
			gc.drawLine(area.x + 1, area.y + 1, area.x + 1, area.y + area.height - 1);
		}
	});
	
	// Create the ToolBarManager and add all the actions to it
	ToolBarManager tbMgr = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL);
	List actions = getOutlineActions();
	for (int i = 0; i < actions.size(); i++) {
		tbMgr.add(new ToolbarDropdownContributionItem(((IAction)actions.get(i))));
	}
	tbMgr.createControl(composite);
	tbMgr.getControl().setFont(composite.getFont());
	
	// By default, the ToolBarManager does not set text on ToolItems.  Since,
	// we want to display the text, we will have to do it manually.
	ToolItem[] items = tbMgr.getControl().getItems();
	for (int i = 0; i < items.length; i++) {
		ToolItem item = items[i];
		item.setText(((IAction)actions.get(i)).getText());
	}
	
	return tbMgr.getControl();
}

/**
 * Creates the TreeViewer that is the outline of the model.
 * 
 * @param	composite	The Composite to which the ToolBar is to be added
 * @return				The newly created TreeViewer
 */
protected TreeViewer createOutlineTreeViewer(Composite composite) {
	Tree treeForViewer = new Tree (composite, SWT.BORDER);
	treeForViewer.setFont(composite.getFont());
	GridData data = new GridData(GridData.FILL_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL);
	data.widthHint = 185;
	// Make the tree this tall even when there is nothing in it.  This will keep the
	// dialog from shrinking to an unusually small size.
	data.heightHint = 200;
	treeForViewer.setLayoutData(data);
	TreeViewer viewer = new TreeViewer(treeForViewer) {
		protected void preservingSelection(Runnable updateCode) {
			if ((getTree().getStyle() & SWT.SINGLE) != 0)
				updateCode.run();
			else
				super.preservingSelection(updateCode);
		}
	};
	viewer.setContentProvider(new PaletteTreeProvider(viewer));
	treeViewerLabelProvider = new PaletteLabelProvider(viewer);
	viewer.setLabelProvider(treeViewerLabelProvider);
	viewer.setInput(getPaletteRoot());
	viewer.addSelectionChangedListener(new ISelectionChangedListener() {
		public void selectionChanged(SelectionChangedEvent event) {
			handleOutlineSelectionChanged();
		}
	});
	
	return viewer;
}

/**
 * Creates the part of the dialog where the properties of the element selected
 * in the outline will be displayed.
 * 
 * <p>
 * The properties panel contains the following:
 * <UL>
 * 		<LI>Title ({@link #createPropertiesPanelTitle(Composite)})</LI>
 * </UL> 
 * The rest of the panel is constructed in this method.
 * </p> 
 * 
 * @param container	The Composite to which this part is to be added
 * @return 			The properties panel
 */
protected Control createPropertiesPanel(Composite container) {
	Composite composite = new Composite(container, SWT.NONE);
	composite.setFont(container.getFont());
	GridLayout layout = new GridLayout(1, false);
	layout.horizontalSpacing = 0;
	layout.marginWidth = 0;
	layout.marginHeight = 0;
	layout.verticalSpacing = 0;
	composite.setLayout(layout);
	
	titleSwitcher = createPropertiesPanelTitle(composite);

	propertiesPanelContainer = new PageBook(composite, SWT.NONE);
	propertiesPanelContainer.setFont(composite.getFont());
	GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL 
	                            | GridData.FILL_VERTICAL);
	data.horizontalSpan = 2;
	propertiesPanelContainer.setLayoutData(data);
	propertiesPanelContainer.addListener(SWT.Resize, new Listener() {
		public void handleEvent(Event event) {
			if (activePage != null) {
				propertiesPanelContainer.layout();
			}
		}
	});
	
	return composite;
}

/**
 * Creates the title for the properties panel.  It is a PageBook that can switch between
 * showing the regular title (the selected entry's label and icon) and an error message if
 * an error has occured.
 * 
 * @param parent	The parent composite
 * @return 		The newly created PageBook title
 */
protected PageBook createPropertiesPanelTitle(Composite parent) {
	GridLayout layout;
	PageBook book = new PageBook(parent, SWT.NONE);
	book.setFont(parent.getFont());
	book.setLayoutData(new GridData(GridData.FILL_HORIZONTAL 
	                              | GridData.VERTICAL_ALIGN_FILL));
	
	titlePage = new Composite(book, SWT.NONE);
	titlePage.setFont(book.getFont());
	layout = new GridLayout(2, false);
	layout.horizontalSpacing = 0;
	layout.marginWidth = 0;
	layout.marginHeight = 0;
	layout.verticalSpacing = 0;
	titlePage.setLayout(layout);
	title = createSectionTitle(titlePage, 
	                           PaletteMessages.NO_SELECTION_TITLE);
	
	errorPage = new Composite(book, SWT.NONE);
	errorPage.setFont(book.getFont());
	layout = new GridLayout(1, false);
	layout.horizontalSpacing = 0;
	layout.marginWidth = 0;
	layout.marginHeight = 0;
	layout.verticalSpacing = 0;
	errorPage.setLayout(layout);
	Composite intermediary = new Composite(errorPage, SWT.NONE) {
		public Point computeSize(int wHint, int hHint, boolean changed) {
			Rectangle bounds = title.getBounds();
			return new Point(bounds.width, bounds.height);
		}
	};
	intermediary.setLayoutData(new GridData(GridData.FILL_HORIZONTAL 
            | GridData.VERTICAL_ALIGN_FILL));
	StackLayout stackLayout = new StackLayout();
	intermediary.setLayout(stackLayout);
	errorTitle = new MultiLineLabel(intermediary);
	stackLayout.topControl = errorTitle;
	errorTitle.setImage(JFaceResources.getImage(DLG_IMG_MESSAGE_ERROR));
	errorTitle.setFont(errorPage.getFont());
	Label separator = new Label(errorPage, SWT.SEPARATOR | SWT.HORIZONTAL);
	separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
	
	book.showPage(titlePage);
	return book;
}

/**
 * A convenient method to create CLabel titles (like the ones used in the
 * Preferences dialog in the Eclipse workbench) throughout the dialog.
 * 
 * @param composite	The composite in which the title is to be created (it must have a
 * 						GridLayout with two columns).
 * @param text			The title to be displayed
 * @return 			The newly created CLabel for convenience
 */
protected CLabel createSectionTitle(Composite composite, String text) {
	CLabel cTitle = new CLabel(composite, SWT.LEFT);
	Color background = JFaceColors.getBannerBackground(composite.getDisplay());
	Color foreground = JFaceColors.getBannerForeground(composite.getDisplay());
	JFaceColors.setColors(cTitle, foreground, background);
	cTitle.setFont(JFaceResources.getBannerFont());
	cTitle.setText(text);
	cTitle.setLayoutData(new GridData(GridData.FILL_HORIZONTAL
	                                | GridData.VERTICAL_ALIGN_FILL));
	
	if (titleImage == null) {
		titleImage = new Image(composite.getDisplay(), 
		             ImageDescriptor.createFromFile(Internal.class, 
		             "icons/customizer_dialog_title.gif").getImageData()); //$NON-NLS-1$
		composite.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				titleImage.dispose();
				titleImage = null;
			}
		});
	}
	
	Label imageLabel = new Label(composite, SWT.LEFT);
	imageLabel.setBackground(background);
	imageLabel.setImage(titleImage);
	imageLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL
	                                     | GridData.VERTICAL_ALIGN_FILL));

	Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
	GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
	data.horizontalSpan = 2;
	separator.setLayoutData(data);

	return cTitle;
}

/**
 * Returns the Button with the given id; or <code>null</code> if none was found.
 * 
 * @see org.eclipse.jface.dialogs.Dialog#getButton(int)
 */
protected Button getButton(int id) {
	Button button = null;
	Widget widget = getWidget(id);
	if (widget instanceof Button) {
		button = (Button)widget;
	}

	return button;
}

/**
 * @return The customizer that is responsible for handling the various tasks
 * and updating the model.
 */
protected PaletteCustomizer getCustomizer() {
	return customizer;
}

/**
 * Returns the <code>EntryPage</code> for the given <code>PaletteEntry</code>.  The 
 * <code>EntryPage</code> is retrieved from the customizer.  If the given entry is 
 * <code>null</code>, <code>null</code> will be returned.  If the customizer returns 
 * <code>null</code> for the valid entry, a default page will be created and returned.
 * 
 * @param entry		The PaletteEntry whose properties need to be displayed
 * @return The EntryPage with the properties of the given PaletteEntry
 */
protected EntryPage getEntryPage(PaletteEntry entry) {
	if (entry == null) {
		return null;
	}
	
	if (entriesToPages.containsKey(entry)) {
		return (EntryPage)entriesToPages.get(entry);
	}
	
	EntryPage page = getCustomizer().getPropertiesPage(entry);
	if (page == null) {
		page = new DefaultEntryPage();
	} 
	page.createControl(propertiesPanelContainer, entry);
	page.setPageContainer(this);
	entriesToPages.put(entry, page);
		
	return page;
}

/**
 * Provides access to the actions that are used to manipulate the model.  The actions will
 * be created, if they haven't been yet.  
 * 
 * @return the list of <code>PaletteCustomizationAction</code>s
 * @see #createOutlineActions()
 */
protected final List getOutlineActions() {
	if (actions == null) {
		actions = createOutlineActions();
	}
	return actions;
}

/**
 * Provides sub-classes with access to the PaletteRoot
 * 
 * @return the palette root
 */
protected PaletteRoot getPaletteRoot() {
	return root;
}

/**
 * @return		The PaletteEntry that is currently selected in the Outline Tree;
 * 				<code>null</code> if none is selected
 */
protected PaletteEntry getSelectedPaletteEntry() {
	TreeItem item = getSelectedTreeItem();
	if (item != null) {
		return (PaletteEntry)item.getData();
	}
	return null;

}

/**
 * @return 	The TreeItem that is currently selected in the Outline Tree; <code>null</code>
 * 				if none is selected
 */
protected TreeItem getSelectedTreeItem() {
	TreeItem[] items = tree.getSelection();
	if (items.length > 0) {
		return items[0];
	}
	return null;
}

/**
 * The <code>Widget</code>s that were created with a unique ID and added to this class'
 * internal map can be retrieved through this method.
 * 
 * @param 	id	The unique ID of the Widget that you wish to retrieve
 * @return 	The Widget, if one with the given id exists; <code>null</code> otherwise
 */
protected Widget getWidget(int id) {
	Widget widget = (Widget)widgets.get(new Integer(id));
	if (widget == null) {
		widget = super.getButton(id);
	}
	
	return widget;
}

/**
 * This method is invoked when the Apply button is pressed
 * <p>
 * IMPORTANT: It is recommended that you not override this method.  Closing the dialog
 * with the 'X' at the top right of the window, or by hitting 'Esc' or any other way,
 * corresponds to a "Cancel."  That will, however, not result in this method being
 * invoked.  To handle such cases, saving or rejecting the changes is handled in {@link
 * #close()}.  Override {@link #save()} and {@link #revertToSaved()} to add to what needs
 * to be done when saving or cancelling.
 * </p>
 */
protected final void handleApplyPressed() {
	save();
}

/**
 * This method is called when the "Delete" action is run (either through the context
 * menu or the toolbar).  It deletes the selected palette entry.
 */
protected void handleDelete() {
	getCustomizer().performDelete(getSelectedPaletteEntry());
	handleOutlineSelectionChanged();
}

/**
 * This method is called when the "Move Down" action is run (either through the context
 * menu or the toolbar).  It moves the selected palette entry down.
 */
protected void handleMoveDown() {
	PaletteEntry entry = getSelectedPaletteEntry();
	getCustomizer().performMoveDown(entry);
	treeviewer.setSelection(new StructuredSelection(entry), true);
	updateActions();
}

/**
 * This method is called when the "Move Up" action is run (either through the context
 * menu or the toolbar).  It moves the selected entry up.
 */
protected void handleMoveUp() {
	PaletteEntry entry = getSelectedPaletteEntry();
	getCustomizer().performMoveUp(entry);
	treeviewer.setSelection(new StructuredSelection(entry), true);
	updateActions();
}

/**
 * This is the method that is called everytime the selection in the outline
 * (treeviewer) changes.  
 */
protected void handleOutlineSelectionChanged() {
	PaletteEntry entry = getSelectedPaletteEntry();
	
	if (activeEntry == entry) {
		return;
	}

	if (errorMessage != null) {
		MessageDialog dialog = new MessageDialog(getShell(),
				PaletteMessages.ERROR,
				null, 
				PaletteMessages.ABORT_PAGE_FLIPPING_MESSAGE 
					+  "\n" + errorMessage, //$NON-NLS-1$
				MessageDialog.ERROR, 
				new String[] {IDialogConstants.OK_LABEL}, 0);
		dialog.open();
		treeviewer.addPostSelectionChangedListener(pageFlippingPreventer);
	} else {
		setActiveEntry(entry);
	}
	updateActions();
}

/**
 * This method is invoked when the changes made since the last save need to be cancelled.
 */
protected void revertToSaved() {
	getCustomizer().revertToSaved();
}

/**
 * This method is invoked when the changes made since the last save need to be saved.
 */
protected void save() {
	if (activePage != null) {
		activePage.apply();
	}
	getCustomizer().save();
}

/**
 * This methods sets the active entry.  Based on the selection, this method
 * will appropriately enable or disable the ToolBar items, will change the CLabel heading
 * of the propreties panel, and will show the properties of the selected item in the
 * properties panel.
 * 
 * @param 	entry	The new active entry, i.e., the new selected entry (it can be
 * 					<code>null</code>)
 */
protected void setActiveEntry(PaletteEntry entry) {
	if (activeEntry != null) {
		activeEntry.removePropertyChangeListener(titleUpdater);
	}
	
	activeEntry = entry;
	
	if (entry != null) {
		title.setText(entry.getLabel());
		Image img = treeViewerLabelProvider.getImage(entry);
		if (img == null) {
			img = getSelectedTreeItem().getImage();
		}
		title.setImage(img);
		entry.addPropertyChangeListener(titleUpdater);
		EntryPage panel = getEntryPage(entry);
		setActiveEntryPage(panel);
	} else {
		title.setImage(null);
		title.setText(PaletteMessages.NO_SELECTION_TITLE);
		//Lazy creation
		if (noSelectionPage == null) {
			noSelectionPage = new EntryPage() {
				private Text text;
				public void apply() {
				}
				public void createControl(Composite parent, PaletteEntry entry) {
					text = new Text(parent, SWT.READ_ONLY);
					text.setFont(parent.getFont());
					text.setText(PaletteMessages.NO_SELECTION_MADE);
				}
				public Control getControl() {
					return text;
				}
				public void setPageContainer(EntryPageContainer pageContainer) {
				}
			};
			noSelectionPage.createControl(propertiesPanelContainer, null);
		}
		setActiveEntryPage(noSelectionPage);
	}
}

/**
 * Sets the given EntryPage as the top page in the PageBook that shows
 * the properties of the item selected in the Outline.  If the given EntryPage
 * is null, nothing will be shown.
 * 
 * @param 	page	The EntryPage to be shown
 */
protected void setActiveEntryPage(EntryPage page) {
	// Have the currently displayed page save its changes
	if (activePage != null) {
		activePage.apply();
	}
	
	if (page == null) { 
		// No page available to display, so hide the panel container
		propertiesPanelContainer.setVisible(false);
	} else {
		// Show the page and grow the shell, if necessary, so that the page is
		// completely visible
		Point oldSize = getShell().getSize();
		propertiesPanelContainer.showPage(page.getControl());

		/*
		 * Fix for bug #34748
		 * There's no need to resize the Shell if initializeBounds() hasn't been called
		 * yet.  It will automatically resize the Shell so that everything fits in the
		 * Dialog.  After that, we can resize the Shell whenever there's an entry page
		 * that cannot fit in the dialog.
		 */
		if (!isSetup) {
			Point newSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
			int x = newSize.x - oldSize.x;
			x = (x < 0) ? 0 : x;
			int y = newSize.y - oldSize.y;
			y = (y < 0) ? 0 : y;
			if (x > 0 || y > 0) {
				getShell().setSize(oldSize.x + x, oldSize.y + y);
			}
		}
		
		// Show the property panel container if it was hidden
		if (!propertiesPanelContainer.isVisible()) {
			propertiesPanelContainer.setVisible(true);
		}
	}
	
	activePage = page;
}

/**
 * Sets the given PaletteEntry as the one to be selected when the dialog
 * opens.  It is discarded when the dialog is closed.
 * 
 * @param	entry	The PaletteEntry that should be selected when the dialog is opened
 */
public void setDefaultSelection(PaletteEntry entry) {
	initialSelection = entry;
}

/**
 * This method should be invoked by EntryPages when there is an error.  It will show the
 * given error in the title of the properties panel.  OK and Apply buttons will be
 * disabled.  Selecting some other entry in the outline tree will not be allowed until the
 * error is fixed.
 * 
 * @see com.architexa.org.eclipse.gef.ui.palette.customize.EntryPageContainer#showProblem(String)
 */
public void showProblem(String error) {
	Assert.isNotNull(error);
	errorTitle.setText(error);
	titleSwitcher.showPage(errorPage);
	getButton(IDialogConstants.OK_ID).setEnabled(false);
	getButton(APPLY_ID).setEnabled(false);
	errorMessage = error;
}

/**
 * Updates the actions created in {@link #createOutlineActions()}, enabling or
 * disabling them as necessary.
 */
protected void updateActions() {
	List actions = getOutlineActions();
	for (Iterator iter = actions.iterator(); iter.hasNext();) {
		PaletteCustomizationAction action = (PaletteCustomizationAction) iter.next();
		action.update();
	}
}

/*
 * Delete Action
 */
private class DeleteAction extends PaletteCustomizationAction {
	public DeleteAction() {
		setEnabled(false);
		setText(PaletteMessages.DELETE_LABEL);
		ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
		setImageDescriptor(sharedImages
				.getImageDescriptor(ISharedImages.IMG_TOOL_DELETE));
		setDisabledImageDescriptor(sharedImages
				.getImageDescriptor(ISharedImages.IMG_TOOL_DELETE_DISABLED));
	}
	
	public void run() {
		handleDelete();
	}
	
	public void update() {
		boolean enabled = false;
		PaletteEntry entry = getSelectedPaletteEntry();
		if (entry != null) {
			enabled = getCustomizer().canDelete(entry);
		}
		setEnabled(enabled);
	}	
}


/*
 * Move Down Action
 */
private class MoveDownAction extends PaletteCustomizationAction {
	public MoveDownAction() {
		setEnabled(false);
		setText(PaletteMessages.MOVE_DOWN_LABEL);
		setImageDescriptor(ImageDescriptor.createFromFile(
				Internal.class, "icons/next_nav.gif"));//$NON-NLS-1$
		setDisabledImageDescriptor(ImageDescriptor.createFromFile(
			Internal.class, "icons/move_down_disabled.gif"));//$NON-NLS-1$
	}
	
	public void run() {
		handleMoveDown();
	}
	
	public void update() {
		boolean enabled = false;
		PaletteEntry entry = getSelectedPaletteEntry();
		if (entry != null) {
			enabled = getCustomizer().canMoveDown(entry);
		}
		setEnabled(enabled);
	}
}


/*
 * Move Up Action
 */
private class MoveUpAction extends PaletteCustomizationAction {
	public MoveUpAction() {
		setEnabled(false);
		setText(PaletteMessages.MOVE_UP_LABEL);
		setImageDescriptor(ImageDescriptor.createFromFile(
				Internal.class, "icons/prev_nav.gif"));//$NON-NLS-1$
		setDisabledImageDescriptor(ImageDescriptor.createFromFile(
			Internal.class, "icons/move_up_disabled.gif")); //$NON-NLS-1$
	}
	
	public void run() {
		handleMoveUp();
	}
	
	public void update() {
		boolean enabled = false;
		PaletteEntry entry = getSelectedPaletteEntry();
		if (entry != null) {
			enabled = getCustomizer().canMoveUp(entry);
		}
		setEnabled(enabled);
	}
}


/*
 * New Action
 */
private class NewAction 
	extends PaletteCustomizationAction 
	implements IMenuCreator
{
	private List factories;
	private MenuManager menuMgr;
	
	public NewAction() {
		factories = wrap(getCustomizer().getNewEntryFactories());
		if (factories.isEmpty()) {
			setEnabled(false);
		} else {
			setMenuCreator(this);
		}
		
		setText(PaletteMessages.NEW_LABEL);
		setImageDescriptor(ImageDescriptor.createFromFile(
						Internal.class, "icons/add.gif")); //$NON-NLS-1$
		setDisabledImageDescriptor(ImageDescriptor.createFromFile(
						Internal.class, "icons/add-disabled.gif")); //$NON-NLS-1$
	}
	
	private void addActionToMenu(Menu parent, IAction action) {
		ActionContributionItem item = new ActionContributionItem(action);
		item.fill(parent, -1);
	}

	public void dispose() {
		if (menuMgr != null) {
			menuMgr.dispose();
			menuMgr = null;
		}
	}

	public Menu getMenu(Control parent) {
		// Create the menu manager and add all the NewActions to it
		if (menuMgr == null) {
			// Lazily create the manager
			menuMgr = new MenuManager();
			menuMgr.createContextMenu(parent);
		}
		
		updateMenuManager(menuMgr);
		return menuMgr.getMenu();
	}

	public Menu getMenu(Menu parent) {
		Menu menu = new Menu(parent);
		for (Iterator iter = factories.iterator(); iter.hasNext();) {
			FactoryWrapperAction action = (FactoryWrapperAction) iter.next();
			if (action.isEnabled()) {
				addActionToMenu(menu, action);
			}
		}
		
		return menu;
	}

	public void run() { }

	public void update() {
		boolean enabled = false;
		PaletteEntry entry = getSelectedPaletteEntry();
		if (entry == null) {
			entry = getPaletteRoot();
		}
		// Enable or disable the FactoryWrapperActions
		for (Iterator iter = factories.iterator(); iter.hasNext();) {
			FactoryWrapperAction action = (FactoryWrapperAction) iter.next();
			action.setEnabled(action.canCreate(entry));
			enabled = enabled || action.isEnabled();
		}
		
		// Enable this action IFF at least one of the new actions is enabled
		setEnabled(enabled);
	}

	protected void updateMenuManager(MenuManager manager) {
		manager.removeAll();
		for (Iterator iter = factories.iterator(); iter.hasNext();) {
			FactoryWrapperAction action = (FactoryWrapperAction) iter.next();
			if (action.isEnabled()) {
				manager.add(action);
			}
		}
	}

	private List wrap(List list) {
		List newList = new ArrayList();
		if (list != null) {
			for (Iterator iter = list.iterator(); iter.hasNext();) {
				PaletteEntryFactory element = (PaletteEntryFactory) iter.next();
				newList.add(new FactoryWrapperAction(element));
			}
		}
				
		return newList;
	}
}


/*
 * FactoryWrapperAction class
 */
private class FactoryWrapperAction extends Action {
	private PaletteEntryFactory factory;
	
	public FactoryWrapperAction(PaletteEntryFactory factory) {
		this.factory = factory;
		setText(factory.getLabel());
		setImageDescriptor(factory.getImageDescriptor());
		setHoverImageDescriptor(factory.getImageDescriptor());
	}
	
	public boolean canCreate(PaletteEntry entry) {
		return factory.canCreate(entry);
	}
	
	public void run() {
		PaletteEntry selected = getSelectedPaletteEntry();
		if (selected == null) selected = getPaletteRoot();
		PaletteEntry newEntry = factory.createNewEntry(getShell(), selected);
		treeviewer.setSelection(new StructuredSelection(newEntry), true);
		updateActions();
	}
}


}
